#include "StdAfx.h"
#include "SpecifiedChunk.h"
#include "ChunkFileSerializer.h"
#include "ChunkSpace.h"
#include "ChunkManager.h"
#include "ChunkCamera.h"
#include "vector3.h"
#include "ChunkIdentifier.h"

#include "ChunkSceneManager.h"

/*
#include <Common/Base/hkBase.h>
#include <Common/Base/Memory/System/Util/hkMemoryInitUtil.h>
#include <Common/Base/Memory/Allocator/Malloc/hkMallocAllocator.h>

// Physics
#include <Physics/Dynamics/World/hkpWorld.h>
#include <Physics/Collide/Dispatch/hkpAgentRegisterUtil.h>
#include <Physics/Dynamics/Entity/hkpRigidBody.h>
#include <Physics/Collide/Shape/Convex/Box/hkpBoxShape.h>

#include <Physics/Utilities/Actions/MouseSpring/hkpMouseSpringAction.h>
*/

using namespace ChunkSystem;
using namespace Common;

const float fMaxScanDist = 5.0f;
const uint16 uMaxNumInLoadingQueue = 20;

const uint16 uMostValuableChunkToLoad = 30;
const uint16 uMostValuableChunkToUnload = 30;
/*
//move it to physics
static void HK_CALL errorReport(const char* msg, void* userContext)
{
	using namespace std;
	printf("%s", msg);
}
*/

ChunkManager::ChunkManager(void)
	:m_pWorkingChunkSpace(NULL),m_pCurCamera(NULL),m_vLastScanPos(Math::NEG_INFINITY,Math::NEG_INFINITY ,Math::NEG_INFINITY ),m_pChunkSceneManager(NULL)
	//,m_pWorld(NULL),m_pMemoryRouter(NULL)
{
	m_workqueue.addRequestHandler(m_gChunkSeekChannel,&m_ChunkSeeker);
	m_workqueue.addRequestHandler(m_gChunkLoadChannel,&m_ChunkLoader);
	m_workqueue.setWorkerThreadCount(3);
	m_workqueue.startup();

	/*
	//setup physics
	// Need to have memory allocated for the solver. Allocate 1mb for it.
	m_pMemoryRouter = hkMemoryInitUtil::initDefault( hkMallocAllocator::m_defaultMallocAllocator, hkMemorySystem::FrameInfo(1024 * 1024) );
	hkBaseSystem::init( m_pMemoryRouter, errorReport );

	hkpWorldCinfo worldInfo;
	//worldInfo.m_simulationType = hkpWorldCinfo::SIMULATION_TYPE_MULTITHREADED;
	worldInfo.setBroadPhaseWorldSize( 60000.0f );

	m_pWorld = new hkpWorld( worldInfo );
	// Register all collision agents
	// It's important to register collision agents before adding any entities to the world.
	hkpAgentRegisterUtil::registerAllAgents( m_pWorld->getCollisionDispatcher() );
	*/

	m_pChunkSceneManager = new ChunkSceneManager;
}

ChunkManager::~ChunkManager(void)
{
	m_workqueue.shutdown();
	if( m_pWorkingChunkSpace != NULL )
		delete m_pWorkingChunkSpace;

	if( m_pChunkSceneManager != NULL )
		delete m_pChunkSceneManager;
}

ChunkCamera* ChunkManager::GetChunkCamera( ChunkCameraFactory* pCamFactory )
{
	if( m_pCurCamera == NULL && pCamFactory != NULL )
		m_pCurCamera = pCamFactory->CreateCamera();

	return m_pCurCamera;
}

void ChunkManager::AbortAllChunkRequest( void )
{
	//Get workqueue and abort all related chunk request and response
}

void ChunkManager::SetResPath( const String& strResPath )
{
	m_strResPath = strResPath;
}

String ChunkManager::GetResPath( void ) const
{
	return m_strResPath;
}

void ChunkManager::SetSpacePath( const String& strSpacePath )
{
	m_strSpacePath = strSpacePath;
}

String ChunkManager::GetSpacePath( void ) const
{
	return m_strSpacePath;
}

void ChunkManager::ChangeChunkSpace( const String& strChunkSpace )
{
	//setup new chunk space
	this->AbortAllChunkRequest();
	if( m_pWorkingChunkSpace != NULL )
		delete m_pWorkingChunkSpace;
	m_pWorkingChunkSpace = new ChunkSpace(strChunkSpace);
}


void ChunkManager::_UpdateLoadingQueue( void )
{
	for( int idx = 0 ; idx < m_loadingQueue.size() ; idx++ )
	{
		Chunk* pLoadedChunk = m_loadingQueue[idx];
		if( pLoadedChunk == NULL )
			continue;

		if( pLoadedChunk->GetCurState() != Chunk::eLoaded )
			continue;

		m_loadingQueue.erase( m_loadingQueue.begin() + (idx--) );

		ChunkSpace* pChunkSpace = m_pCurCamera->GetCurChunkSpace();
		if( pChunkSpace == NULL )
			continue;

		pLoadedChunk->Bind();

		//ExternalChunk* pExternalChunk = static_cast< ExternalChunk* >( pLoadedChunk );
		//if( pExternalChunk == NULL )
		pChunkSpace->Focus( m_pCurCamera->GetPos() );
	}
}

void ChunkManager::Draw()
{
	if( m_pWorkingChunkSpace == NULL )
		return;

}

void ChunkManager::Tick(void)
{
	if( m_pCurCamera == NULL )
		return;

	//step0: update loading queue
	this->_UpdateLoadingQueue();

	//step1: seeking for chunks to create. 
	//seeking step :
	//0. from point test by existing hulltree in the Column

	//1. if column is NULL, try to solve blind area
	Chunk* pCameraChunk = m_pCurCamera->GetCurChunk();
	ChunkSpace* pCameraChunkSpace = m_pCurCamera->GetCurChunkSpace();
	if( pCameraChunkSpace == NULL )
		m_pCurCamera->SetCurChunkSpace( m_pWorkingChunkSpace );

	pCameraChunkSpace = m_pCurCamera->GetCurChunkSpace();
	Vector3 vCameraPos = m_pCurCamera->GetPos();
	if( pCameraChunk == NULL )
	{
		pCameraChunk = pCameraChunkSpace->GetChunkByPointTest( vCameraPos );
		if( pCameraChunk == NULL && m_pCurCamera->GetCurChunkSpace()  != NULL )
		{
			this->_SolveBlind( vCameraPos , m_pCurCamera->GetCurChunkSpace() );
		}
	}
	else
	{
		//check weather we should scan again
		//scan most valuable chunks to load
		Vector3 vDistance = m_pCurCamera->GetPos() - m_vLastScanPos;
		if( vDistance.length() > fMaxScanDist )
			this->_Scan();

		Vector3 vCurPos = m_pCurCamera->GetPos();
		Chunk* pUpdateCurCameraInChunk = m_pWorkingChunkSpace->GetChunkByGridCoord( m_pWorkingChunkSpace->PosToGridCoord( vCurPos ) );
		m_pCurCamera->SetCurChunk( pUpdateCurCameraInChunk );
	}

	//search again and reset precise camera chunk
	pCameraChunk = pCameraChunkSpace->GetChunkByPointTest( vCameraPos );
	if( pCameraChunk != NULL )
		m_pCurCamera->SetCurChunk( pCameraChunk );
}

void ChunkManager::LoadChunk( Chunk* pChunk )
{
	assert( pChunk != NULL );

	Chunk::State& eChunkState = pChunk->GetCurState();
	if( eChunkState != Chunk::eUnload )
		return;

	for( LoadingQueue::iterator pos = m_loadingQueue.begin(); pos != m_loadingQueue.end(); pos++ )
	{
		if( pChunk->GetId() == (*pos)->GetId() )
			return;
	}

	ChunkLoadRequestData objChunkLoadRequestData;
	objChunkLoadRequestData.m_pLoadTarget= pChunk;

	eChunkState = Chunk::eLoading;

	this->m_workqueue.addRequest(m_gChunkLoadChannel, m_gChunkLoadChannel, Any( objChunkLoadRequestData ) );

	m_loadingQueue.push_back(pChunk);
}

void ChunkManager::_SolveBlind(const Common::Vector3& rPos , ChunkSpace* pCameraChunkSpace)
{
	// first see if we are already finding a seed
	Chunk * pFoundSeek = m_ChunkSeeker.GetFoundSeed();
	if ( pFoundSeek == NULL )
	{
		ChunkSeekRequestData seekData;
		seekData.m_pChunkFileSerializer = this->m_pChunkSerialzer;
		seekData.m_pWorkSpace = this->m_pWorkingChunkSpace;
		seekData.m_vTargetPos = rPos;

		m_workqueue.addRequest( m_gChunkSeekChannel , m_gChunkSeekChannel , Any( seekData ) );
	}
	// see if the operation has completed
	else
	{
		// see if it was for our space
		if (pFoundSeek->GetOwnerSpace() == m_pWorkingChunkSpace)
		{
			m_pWorkingChunkSpace->AddIdenticalChunk( pFoundSeek );

			Chunk::State eChunkState = pFoundSeek->GetCurState();
			if (eChunkState == Chunk::eUnload)
				this->LoadChunk( pFoundSeek );

			m_pCurCamera->SetCurChunk( pFoundSeek );
		}
		else
		{
			// otherwise just delete it and wait to be called again
			delete pFoundSeek;
		}

		m_ChunkSeeker.ResetSeed();

		// note that if the operation completed but did not find any chunk
		// then pFoundSeed_ will be set to NULL, which is exactly what we want
	}
}

struct RadiationGridComp
{
	bool operator()( GridCoord grid1, GridCoord grid2 )
	{
		return grid1.first * grid1.first + grid1.second * grid1.second <
			grid2.first * grid2.first + grid2.second * grid2.second;
	}
};

//scan to get new objects to load and release
void ChunkManager::_Scan( void )
{
	static int savedMaxLoadGrid = 0;
	static std::vector<GridCoord> sortedGridBounds;

	//step0:calculate load
	float fLoadingRange = m_pCurCamera->GetLoadingRange();
	int iMaxLoadingGrid = (int)floor( fLoadingRange / m_pWorkingChunkSpace->GetGridResolution() );

	if( savedMaxLoadGrid < iMaxLoadingGrid )
	{
		savedMaxLoadGrid = iMaxLoadingGrid;

		sortedGridBounds.resize( 0 );
		sortedGridBounds.reserve( ( savedMaxLoadGrid * 2 + 1 ) * ( savedMaxLoadGrid * 2 + 1 ) );

		for( int i = -savedMaxLoadGrid; i < savedMaxLoadGrid + 1; ++i )
		{
			for( int j = -savedMaxLoadGrid; j < savedMaxLoadGrid + 1; ++j )
			{
				sortedGridBounds.push_back( std::make_pair( i, j ) );
			}
		}

		std::sort( sortedGridBounds.begin(), sortedGridBounds.end(), RadiationGridComp() );
	}

	m_vLastScanPos = m_pCurCamera->GetPos();

	m_bNoneLoadedAtLastScan = true;	

	Chunk* pMostWorthy[uMostValuableChunkToLoad] = { NULL };
	size_t uMostWorthSize = 0;

	GridCoord& rCameraGrid = m_pWorkingChunkSpace->PosToGridCoord( m_vLastScanPos );

	for( std::vector<GridCoord>::iterator iter = sortedGridBounds.begin();iter != sortedGridBounds.end(); ++iter )
	{
		//Vector3 pos = m_pWorkingChunkSpace->GridCoordToPos( *iter );
		//if( ::pow( pos.x, 2 ) + ::pow( pos.z, 2 ) > ::pow(fLoadingRange, 2) )
		//	continue;

		int x = rCameraGrid.first  + iter->first;
		int z = rCameraGrid.second + iter->second;

		Chunk* pChunk = m_pWorkingChunkSpace->GetChunkByGridCoord( GridCoord(x,z) );
		if( pChunk == NULL )
		{
			// make the chunk
			String strChunkId = ChunkIdentifier::GridCoordToExternalChunkPath(GridCoord(x,z));

			pChunk = new ExternalChunk(strChunkId, m_pWorkingChunkSpace);
			// add it to its space's map of chunks
			m_pWorkingChunkSpace->AddIdenticalChunk( pChunk );
		}

		if( pChunk != NULL && pChunk->GetCurState() == Chunk::eUnload )
		{
			pMostWorthy[uMostWorthSize++] = pChunk;
			if(uMostWorthSize >= uMostValuableChunkToLoad)
				break;
		}
	}

	//step1: calculate unloaded one
	// Get the position of the grid square the camera is in
	std::vector< Chunk* > furthestChunks;
	furthestChunks.reserve( uMostValuableChunkToUnload );

	float fHoldingRange = m_pCurCamera->GetHoldingRange();
	int iMinUnloadingRange = (int)floor( fHoldingRange / m_pWorkingChunkSpace->GetGridResolution() );

	const ChunkSpace::CurrentChunkMap& rChunkMap = m_pWorkingChunkSpace->GetCurrentChunk();
	for ( ChunkSpace::CurrentChunkMap::const_iterator pos = rChunkMap.begin(); pos != rChunkMap.end() ; ++pos)
	{
		Chunk* pMapChunk = pos->second;
		if( pMapChunk == NULL )
			continue;

		Chunk::State eCurState = pMapChunk->GetCurState();
		if( eCurState != Chunk::eLoaded && eCurState != Chunk::eBinded )
			continue;

		Vector3 center = pMapChunk->GetWorldTM().getTrans();
		if( ::pow((center.x - m_vLastScanPos.x), 2) + ::pow((center.z - m_vLastScanPos.z), 2) <= ::pow(fHoldingRange, 2) )
			continue;

		if( pMapChunk->GetAABB().intersects(m_vLastScanPos) )
			continue;

		furthestChunks.push_back(pMapChunk);

		if( furthestChunks.size() > uMostValuableChunkToUnload )
			break;
	}

	for( size_t uMostWorthIdx = 0; uMostWorthIdx < uMostWorthSize; ++uMostWorthIdx )
	{
		if(m_loadingQueue.size() > uMaxNumInLoadingQueue)
			break;

		Chunk *chunk = pMostWorthy[uMostWorthIdx];
		this->LoadChunk(chunk);
		m_bNoneLoadedAtLastScan = false;
	}

	// also get rid of the furthest one if it is far enough away
	if( furthestChunks.empty() )
		return;

	for (std::vector< Chunk * >::iterator it = furthestChunks.begin();
		it != furthestChunks.end(); ++it)
	{
		// cut it free from its bindings
		(*it)->Loose();

		// and clean it out
		(*it)->Unload();

		m_pWorkingChunkSpace->RemoveChunk(*it);

		LoadingQueue::iterator pos = std::find( m_loadingQueue.begin(), m_loadingQueue.end(), *it );
		if( pos != m_loadingQueue.end() )
			m_loadingQueue.erase(pos);
	}

}
/*
hkpWorld* ChunkManager::GetCurPhysicsWorld( void ) const
{
	return m_pWorld;
}
*/

ChunkSpace* ChunkManager::GetCurChunkSpace( void ) const
{
	return m_pWorkingChunkSpace;
}